package javango.contrib.hibernate;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.context.ManagedSessionContext;
import org.hibernate.criterion.Projections;

import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * Basic Hibernate helper class, handles SessionFactory, Session and Transaction.
 * <p>
 * Uses a  initializer for the initial SessionFactory creation
 * and holds Session and Transactions in thread local variables. All
 * exceptions are wrapped in an unchecked DaoException.
 *
 * @author johns
 */

@Singleton
public class HibernateUtilImpl implements HibernateUtil {

	private  Log log = LogFactory.getLog(HibernateUtilImpl.class);

	private AnnotationConfiguration configuration;
	private SessionFactory sessionFactory;
	private Interceptor interceptor; 

	@Inject
	public HibernateUtilImpl(AnnotationConfiguration configuration,
			SessionFactory sessionFactory) {
		super();
		this.configuration = configuration;
		this.sessionFactory = sessionFactory;
	}

	/**
	 * Returns the SessionFactory used for this  class.
	 *
	 * @return SessionFactory
	 */
	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	/**
	 * Returns the original Hibernate configuration.
	 *
	 * @return Configuration
	 */
	public AnnotationConfiguration getConfiguration() {
		return configuration;
	}
	/**
	 * Retrieves the current Session local to the thread.
	 * <p/>
	 * If no Session is open, opens a new Session for the running thread.
	 *
	 * @return Session
	 */
	public Session getSession()	throws HibernateException {
		if (ManagedSessionContext.hasBind(sessionFactory)) {
			return getSessionFactory().getCurrentSession();
		}
		org.hibernate.classic.Session s;
		if (interceptor != null) {
			s = getSessionFactory().openSession(interceptor);
		} else {
			s = getSessionFactory().openSession();
		}
		ManagedSessionContext.bind(s);
		return s;
	}

	/**
	 * Closes the Session local to the thread.
	 */
	public void closeSession() throws HibernateException {
		Session s = getSession();
		s.close();
		ManagedSessionContext.unbind(sessionFactory);
	}

	/**
	 * Start a new database transaction.
	 */
	public void beginTransaction()
		throws HibernateException {
		try {
			Session s = getSession();
			Transaction tx = s.getTransaction();
			tx.begin();
		} catch (HibernateException ex) {
			log.warn("Hibernate Exception during begin: " + ex.getMessage() , ex);
			throw ex;
		} catch (Exception ex) {
			log.warn("Exception during begin: " + ex.getMessage() , ex);
			throw new HibernateException(ex.getMessage());
		}
	}

	/**
	 * Commit the database transaction.
	 */
	public void commitTransaction()
		throws HibernateException {
		try {
			Transaction t = getSession().getTransaction();
			if (t.isActive()) {
				t.commit();
			} else {
				log.info("Transaction not active");
			}
		} catch (HibernateException ex) {
			log.warn("Hibernate exception during commit " + ex.getMessage() , ex);
			rollbackTransaction();
			throw ex;
		}
	}

	/**
	 * Commit the database transaction.
	 */
	public void rollbackTransaction() throws HibernateException {
		try {
			Transaction t = getSession().getTransaction();
			if (t.isActive()) {
				t.rollback();
			} else {
				log.info("Transaction not active");
			}
		} catch (HibernateException ex) {
			log.warn("Hibernate Exception during rollback: " + ex.getMessage() , ex);
			throw ex;
		} catch (Exception ex) {
			log.warn("Exception during rollback: " + ex.getMessage() , ex);
			throw new HibernateException(ex.getMessage());
		}
	}

	/**
	 * Uses hibernate3's projections to get a count of the number of results that this criteria would return.
	 *
	 * @param c
	 */
	public  int doCount(Criteria c) throws HibernateException {
		int value = 0;

		c.setProjection(Projections.rowCount());
		Object o = c.uniqueResult();
		if (o == null) {
			value = 0;
		} else if (o instanceof Number ) {
			Number count = (Number)o;
			value = count.intValue();
		} else {
			throw new HibernateException("Unable to get count");
		}
		
		c.setProjection( null ); 
		c.setResultTransformer( Criteria.ROOT_ENTITY );

		return value;
	}

	public Interceptor getInterceptor() {
		return interceptor;
	}

	public void setInterceptor(Interceptor interceptor) {
		this.interceptor = interceptor;
	}
}
